 /* Copyright (C)
  * 2024 ZKLX Optoelectronic Technology Co.LTD
  * All rights reserved.
  */
 /**
  * @file web_viewer.cpp
  * @brief jcvp web engine
  * @author xuke
  * @version 1.0
  * @date 2024-10-15
  */

#include "/home/xuke/bin/dbg.h"
#include <iostream>
#include <fstream>
#include <thread>
#include <unistd.h>
//#include <opencv2/opencv.hpp>
#include <nlohmann/json.hpp>
#include "jcvp.h"

using njson = nlohmann::json;

/*--------------------------------extern--------------------------------------------*/

/*--------------------------------globals-------------------------------------------*/
JCVP_T *g_jcvp_obj = NULL;

/*--------------------------------locals--------------------------------------------*/
std::string sec_op_passwd = "EExuke123";

/*--------------------------------funcs---------------------------------------------*/
std::string base64decode(const char* data, size_t size)
{/*{{{*/
	// We accept both regular and url encoding here, as there does not seem to be any downside to that.
	// If we want to distinguish that we should use +/ for non-url and -_ for url.

	// Mapping logic from characters to [0-63]
	auto key = [](char c) -> unsigned char {
		if ((c >= 'A') && (c <= 'Z')) return c - 'A';
		if ((c >= 'a') && (c <= 'z')) return c - 'a' + 26;
		if ((c >= '0') && (c <= '9')) return c - '0' + 52;
		if ((c == '+') || (c == '-')) return 62;
		if ((c == '/') || (c == '_')) return 63;
		return 0;
	};

	// Not padded
	if (size % 4 == 2)             // missing last 2 characters
		size = (size / 4 * 3) + 1; // Not subtracting extra characters because they're truncated in int division
	else if (size % 4 == 3)        // missing last character
		size = (size / 4 * 3) + 2; // Not subtracting extra characters because they're truncated in int division

	// Padded
	else if (data[size - 2] == '=') // padded with '=='
		size = (size / 4 * 3) - 2;  // == padding means the last block only has 1 character instead of 3, hence the '-2'
	else if (data[size - 1] == '=') // padded with '='
		size = (size / 4 * 3) - 1;  // = padding means the last block only has 2 character instead of 3, hence the '-1'

	// Padding not needed
	else
		size = size / 4 * 3;

	std::string ret;
	ret.resize(size);
	auto it = ret.begin();

	// These will be used to decode 1 character at a time
	unsigned char odd;  // char1 and char3
	unsigned char even; // char2 and char4

	// Take 4 character blocks to turn into 3
	while (size >= 3)
	{
		// dec_char1 = (char1 shifted 2 bits to the left) OR ((char2 AND 00110000) shifted 4 bits to the right))
		odd = key(*data++);
		even = key(*data++);
		*it++ = (odd << 2) | ((even & 0x30) >> 4);
		// dec_char2 = ((char2 AND 00001111) shifted 4 bits left) OR ((char3 AND 00111100) shifted 2 bits right))
		odd = key(*data++);
		*it++ = ((even & 0x0F) << 4) | ((odd & 0x3C) >> 2);
		// dec_char3 = ((char3 AND 00000011) shifted 6 bits left) OR (char4)
		even = key(*data++);
		*it++ = ((odd & 0x03) << 6) | (even);

		size -= 3;
	}
	if (size == 2)
	{
		// d_char1 = (char1 shifted 2 bits to the left) OR ((char2 AND 00110000) shifted 4 bits to the right))
		odd = key(*data++);
		even = key(*data++);
		*it++ = (odd << 2) | ((even & 0x30) >> 4);
		// d_char2 = ((char2 AND 00001111) shifted 4 bits left) OR ((char3 AND 00111100) shifted 2 bits right))
		odd = key(*data++);
		*it++ = ((even & 0x0F) << 4) | ((odd & 0x3C) >> 2);
	}
	else if (size == 1)
	{
		// d_char1 = (char1 shifted 2 bits to the left) OR ((char2 AND 00110000) shifted 4 bits to the right))
		odd = key(*data++);
		even = key(*data++);
		*it++ = (odd << 2) | ((even & 0x30) >> 4);
	}
	return ret;
}/*}}}*/

/**
 * @brief 回复状态字符串
 * @param resp_string
 * @param show_mode: inline, newline, clear
 * @return 0-success
 */
int resp_run_state(std::string resp_string, std::string show_mode)
{
	if (g_jcvp_obj && g_jcvp_obj->_conn) {
		njson resp_prog_obj;
		resp_prog_obj["type"] = "run_state";
		resp_prog_obj["show_mode"] = show_mode;
		resp_prog_obj["state_str"] = resp_string;
		g_jcvp_obj->jc_send(resp_prog_obj.dump());
	} else {
		return -1;
	}
	return 0;
}

//run install
int run_deb_package_install(const char *file_path)
{
	std::string state_string = "sudo dpkg -i " + std::string(file_path) + " 2>&1";
	resp_run_state(state_string, "newline");

	FILE *pp = popen(state_string.c_str(), "r");
	if (!pp) {
		resp_run_state("ERROR: popen CMD failed!!!", "newline");
		return -1;
	}
	char line_buf[256] = {0};
	while (nullptr != fgets(line_buf, sizeof(line_buf), pp)) {
		line_buf[strlen(line_buf)-1] = '\0';
		resp_run_state(std::string(line_buf), "newline");
	}
	pclose(pp);
	return 0;
}

// video play output
static void jcvp_vp_output_task(void *args)
{
	//cv::Mat out_img;

	while (true) {
		if (g_jcvp_obj && g_jcvp_obj->_conn) {
			//g_frame_render.getFrame(out_img);
			//g_jcvp_obj->frame_update(out_img);
		}

		usleep(1000*1000);
	}
}

// json ctrl reciver
static void jcvp_jc_recive_task(void *args)
{
	std::string jc_str;
	char *cmd_type = NULL;
	uint32_t echo_cnt = 0;
	uint32_t recv_size = 0;
	int32_t last_index = 0;
	std::ofstream *output_file = NULL;
	std::string file_path;

	while (true) {
		if (!g_jcvp_obj->_conn) { //is connected
			sleep(1);
			continue;
		}

		jc_str = g_jcvp_obj->jc_recv();
		//log_d("jc:[%s]", jc_str.c_str());

		/* parase json data */
		njson root_obj = njson::parse(jc_str);
		if (root_obj["type"] == "upload") {
			//密码验证
			if (sec_op_passwd != std::string(root_obj["passwd"])) {
				resp_run_state("ERROR: Operation password error!", "newline");
				continue;
			}

			if (1 == (int)root_obj["cur_chunk"]) { //first chunk
				file_path = "/tmp/" + std::string(root_obj["filename"]);
				output_file = new std::ofstream(file_path, std::ios::binary|std::ios::trunc);
				if (!output_file->is_open()) {
					resp_run_state(std::string(root_obj["filename"]) + " file open error!", "newline");
					continue;
				} else {
					recv_size = 0;
					last_index = 0;
				}
			}
			if ((last_index+1) == (int)root_obj["cur_chunk"]) {
				last_index++;

				//write file
				std::string raw_data = root_obj["data"];
				std::string decode_string = base64decode(raw_data.data(), raw_data.size());
				output_file->write(reinterpret_cast<char*>(decode_string.data()), decode_string.size());

				//response run_state
				recv_size += (int)root_obj["cur_size"];
				char run_state_str[128] = {0};
				snprintf(run_state_str, sizeof(run_state_str), "upload file: [%s], chunk:%d/%d, size:%d/%d, cur_size=%d", std::string(root_obj["filename"]).c_str(), (int)root_obj["cur_chunk"], (int)root_obj["all_chunk"], recv_size, (int)root_obj["file_size"], (int)root_obj["cur_size"]);
				resp_run_state(std::string(run_state_str), "inline");
			}
			//all recved, run
			if ((int)root_obj["cur_chunk"] == (int)root_obj["all_chunk"]) {
				output_file->close();
				resp_run_state(std::string(root_obj["filename"]) + " all recved successfull!", "newline");
				//run install
				run_deb_package_install(file_path.c_str());
				resp_run_state("END: run update cmds completed.", "newline");
			}

			//response recved
			njson resp_recv_obj;
			resp_recv_obj["type"] = "upload_resp";
			resp_recv_obj["state"] = true;
			resp_recv_obj["index"] = (int)root_obj["cur_chunk"];
			g_jcvp_obj->jc_send(resp_recv_obj.dump());
		}
	}
}

// json status sender
static void jcvp_js_update_task(void *args)
{
	char date_str[32] = {0};
	char time_str[32] = {0};

	while (true) {
		if (g_jcvp_obj->_conn) { //is connected
			if (0) { //run state
				time_t cur_time = time(NULL);
				strftime(date_str, sizeof(date_str), "%Y-%m-%d", localtime(&cur_time));
				strftime(time_str, sizeof(time_str), "%H:%M:%S", localtime(&cur_time));
				njson root_obj;
				root_obj["type"] = "run_state";
				root_obj["state_str"] = std::string(date_str) + " " + std::string(time_str);
				g_jcvp_obj->jc_send(root_obj.dump());
			}
		}

		sleep(1);
	}
}

int web_viewer_init(std::string web_root, int port, int fps, float img_scaling)
{
	// init jcvp_web server
	g_jcvp_obj = new JCVP_T(web_root, port, fps, img_scaling); //web_root, fps, img_scaling
	g_jcvp_obj->webs_run();

	// vp task
	//std::thread jcvp_vp_thread(jcvp_vp_output_task, nullptr);
	//jcvp_vp_thread.detach();

	// jc task
	std::thread jcvp_jc_thread(jcvp_jc_recive_task, nullptr);
	jcvp_jc_thread.detach();

	// js task
	std::thread jcvp_js_thread(jcvp_js_update_task, nullptr);
	jcvp_js_thread.detach();

	return 0;
}

